package org.baicaixiaozhan.learncommon.util;

import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.MultipartHttpServletRequest;

import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.URLEncoder;
import java.nio.channels.FileChannel;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.UUID;

/**
 * 文件操作工具类
 *
 * @since 2021/6/30
 * @author baicaixiaozhan
 */
public final class SimpleFileUtils {

    private SimpleFileUtils() {
        throw new AssertionError();
    }


    /**
     * 本方法是 MultipartFile 单文件上传专用方法,使用此方法，你需要知道以下几点(以linux系统为例):
     * 你需要一个指定的 MultipartFile。
     * filePathAndFileName 是一个可选参数，你可能会有以下几种情况:
     * 指定父目录、多级目录和文件名。例如:fileUpload(multipartFile, "/home/file/","txt","001","1.txt");
     * 一个完整的文件路径。例如:fileUpload(multipartFile, "/home/file/txt/001/1.txt");
     * 不指定文件名。例如:fileUpload(multipartFile, "/home/file/","txt","001")。这种情况会使用multipartFile文件的原始文件名。
     * 推荐使用{指定父目录、多级目录不指定文件名}的调用方式,假如你需要指定文件名，也请使用{指定父目录、多级目录和文件名}的方式。
     * 不建议自己拼接目录，请仔细检查路径是否合法(文件路径不应该包含.^%$#@&等特殊字符)。
     *
     * @param multipartFile       文件
     * @param filePathAndFileName 可选参数
     * @return 文件路径
     */
    public static String uploadFile(MultipartFile multipartFile, String... filePathAndFileName) {
        String filePathStr = getRealFilePath(filePathAndFileName);
        createPaths(filePathStr);
        File file = new File(filePathStr);
        try {
            multipartFile.transferTo(file);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return filePathStr;
    }

    /**
     * 本方法是单文件传输专用方法,使用此方法，你需要知道以下几点(以linux系统为例):
     * 你需要一个完整的文件路径，例如:/home/file/txt/001/1.txt。
     * 如果上传的文件已经存在，则提示异常。
     * filePathAndFileName参数使用说明和MultipartFile单文件上传专用方法一致。
     * 其他注意事项和MultipartFile单文件上传专用方法一致。
     *
     * @param localFilePath       完整的文件路径
     * @param filePathAndFileName 目标文件
     * @return 文件路径
     * <code>{@link #fileUpload(MultipartFile, String...)}</code>.
     * @see StandardOpenOption
     */
    public static String fileUpload(String localFilePath, String... filePathAndFileName) {
        String filePathStr = getRealFilePath(filePathAndFileName);
        createPaths(filePathStr);
        try (FileChannel inChannel = FileChannel.open(Paths.get(localFilePath), StandardOpenOption.READ); FileChannel outChannel = FileChannel.open(Paths.get(filePathStr), StandardOpenOption.WRITE, StandardOpenOption.READ, StandardOpenOption.CREATE_NEW)) {
            inChannel.transferTo(0, inChannel.size(), outChannel);
        } catch (IOException e) {
            e.printStackTrace();
        }
        return filePathStr;
    }

    /**
     * 本方法是MultipartFile多文件文件上传专用方法，使用此方法，你需要知道以下几点(以linux系统为例):
     * 你需要一个包含多个MultipartFile文件的HttpServletRequest对象。
     * multipartFileKey是一个选填参数，默认"file"是你上传多个文件的name,如果你的name不是"file"，则需要指定multipartFileKey。
     * 本方法只能将多个文件保存在同一个目录，且无法指定文件名。即:multiFileUpload(request, "/home/file/","txt","001")。
     * 推荐使用{指定父目录、多级目录不指定multipartFileKey}的调用方式。
     * 仔细检查文件路径是否合法。
     *
     * @param request          包含多个MultipartFile文件的HttpServletRequest对象
     * @param multipartFileKey 文件的name
     * @param filePathParams   文件目录
     * @return 多个文件的文件路径
     */
    public static ArrayList<String> multiFileUpload(HttpServletRequest request, String multipartFileKey, String... filePathParams) {
        String filePathStr = getRealFilePath(filePathParams);
        createPaths(filePathStr);
        List<MultipartFile> multipartFileList;
        if (multipartFileKey != null && !"".equals(multipartFileKey)) {
            multipartFileList = ((MultipartHttpServletRequest) request).getFiles(multipartFileKey);
        } else {
            multipartFileList = ((MultipartHttpServletRequest) request).getFiles("file");
        }
        ArrayList<String> filePathList = new ArrayList<>();
        for (MultipartFile multipartFile : multipartFileList) {
            String fileName = UUID.randomUUID().toString();
            String realFilePath = filePathStr + fileName + getRealFileSuffix(Objects.requireNonNull(multipartFile.getOriginalFilename()));
            try {
                multipartFile.transferTo(new File(realFilePath));
            } catch (IOException e) {
                e.printStackTrace();
            }
            filePathList.add(realFilePath);
        }
        return filePathList;
    }



    /**
     * 浏览器单文件下载专用方法，使用此方法，你需要知道以下几点(以linux系统为例):
     * 你需要一个HttpServletResponse对象。
     * filePathAndFileName参数是一个可选参数，你可能有以下几种调用方式:
     * fileDownload(response,"/home/file/","txt","001","1.txt")
     * fileDownload(response,"/home/file/txt/001","1.txt")
     * fileDownload(response,"/home/file/txt/001/1.txt"）
     * 不管哪种方式，你都必须指定{文件名},确保filePathAndFileName能组合成一个完整的文件路径
     * 仔细检查文件路径是否合法。
     *
     * @param response            HttpServletResponse对象
     * @param filePathAndFileName 文件路径
     * @return 下载状态
     * <code>{@link #download(HttpServletResponse, String, String)} (MultipartFile, String...)}</code>.
     */
    public static String fileDownload(HttpServletResponse response, String... filePathAndFileName) {
        String filePath = getRealFilePath(filePathAndFileName);
        String fileName = getRealFileName(filePath);
        return download(response, filePath, fileName);
    }

    /**
     * 浏览器单文件下载专用方法,可指定文件名下载。
     *
     * @return 下载状态
     * <code>{@link #fileDownload(HttpServletResponse, String...)} (HttpServletResponse, String, String)} (MultipartFile, String...)}</code>.
     */
    public static String fileDownload(String fileName,HttpServletResponse response,  String... filePathAndFileName) {
        String filePath = getRealFilePath(filePathAndFileName);
        return download(response, filePath, fileName);
    }

    private static String download(HttpServletResponse response, String filePath, String fileName) {
        File file = new File(filePath);
        if (file.exists()) {
            // 设置文件名和默认下载文件
            response.setContentType("application/force-download");
            try {
                fileName = URLEncoder.encode(fileName, "UTF-8");
                fileName = fileName.replaceAll("\\+", "%20");
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            }
            response.addHeader("Content-Disposition", "attachment;fileName=" + fileName);
            // 文件传输
            FileInputStream fi = null;
            try {
                byte[] buffer = new byte[4096];
                fi = new FileInputStream(file);
                ServletOutputStream outputStream = response.getOutputStream();
                int i;
                while (-1 != (i = fi.read(buffer))) {
                    outputStream.write(buffer, 0, i);
                }
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                try {
                    assert fi != null;
                    fi.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            return "下载成功";
        } else {
            return "文件不存在!";
        }
    }
    /**
     * 判断文件路径是否存在,不存在则创建目录
     *
     * @param filePath 文件路径
     */
    public static void createPaths(String filePath) {
        File file = new File(filePath);
        File parentFile = file.getParentFile();
        if (!parentFile.exists()) {
            boolean mkdir = parentFile.mkdirs();
            if (!mkdir) {
                try {
                    throw new IOException("无法创建文件夹!");
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    /**
     * 通过多个可选参数获取完整的文件目录/文件路径
     *
     * @param filePathParams 包含父目录、多级目录或文件名的多个字符
     * @return 文件目录/文件路径
     */
    private static String getRealFilePath(String[] filePathParams) {
        StringBuilder filePath = new StringBuilder();
        for (String s : filePathParams) {
            filePath.append(s);
            if (!s.endsWith(File.separator) && !s.contains(".")) {
                filePath.append(File.separator);
            }
        }
        return String.valueOf(filePath);
    }

    /**
     * 通过完整的文件路径获取文件名
     *
     * @param filePath 文件路径
     * @return 文件名
     */
    public static String getRealFileName(String filePath) {
        return filePath.substring(filePath.lastIndexOf(File.separator) + 1);
    }

    public static String getRealFileNameInFullName(String fullName) {
        return fullName.substring(0, fullName.lastIndexOf("."));
    }

    /**
     * 通过完整的文件路径获取文件后缀
     *
     * @param filePath 文件路径
     * @return 文件后缀
     */
    public static String getRealFileSuffix(String filePath) {
        return filePath.substring(filePath.lastIndexOf(".") + 1);
    }
}
